#include "stdafx.h"
#include "SocketServer.h"
#include "WindowsUtility.h"

// Make sure if you enable G_TRACE_METHOD, that you've initialized the logger in your setup!
#if 1		// DEBUG -- hansohn, 15 May 2009
//    #define TRACE_ENTRY(m)
	#define TRACE_ENTRY(m) G_TRACE_METHOD(m)
	#define SS_TRACE0 G_LOG_0_TRACE
	#define SS_TRACE1 G_LOG_1_TRACE
#else
    #define TRACE_ENTRY(m)
	#define SS_TRACE0 G_LOG_0_TRACE
	#define SS_TRACE1 G_LOG_1_TRACE
#endif

namespace RoomFW {

SocketServer::SocketServer(void) :
	stopEvent_(),
	port_(0),
	address_(),
	listener_(),
	client_(),
	connectQueue_(),
	connectQueueSynch_(),
	connectQueueEvent_()
{
}

SocketServer::~SocketServer(void)
{
	G_LOG_2_TRACE("SocketServer::~SocketServer: '%s':%d", address_, port_);
	Thread::stop(); // Before member destructors.
}

bool SocketServer::start(int port, const char * address, uint32_t timeout)
{
	TRACE_ENTRY("SocketServer::start");

	ASSERT(!this->isRunning()); // Client error

	// Save args and call base.
	port_ = port;
	address_ = address;
	return Thread::start(timeout);
}

SimpleSocket::SocketPtr SocketServer::connect(int port, const char* address)
{
	TRACE_ENTRY("SocketServer::connect");

	// Create socket.
	SimpleSocket::SocketPtr retVal(new SimpleSocket());
	ASSERT(retVal);

	// Try to connect.
	if (retVal && retVal->connect(port, address))
	{
		// Create and associate read event.
		SocketEventSP socketEvent(new SocketEvent(retVal));
		if (socketEvent && socketEvent->initialize(FD_READ | FD_CLOSE))
		{
			// Save it.
			CriticalSectionGuard g(connectQueueSynch_);
			connectQueue_.push(socketEvent);
			connectQueueEvent_.set();
		}
		else
		{
			retVal.reset();
		}
	}
	else
	{
		retVal.reset();
	}
	return retVal;
}


bool SocketServer::initializeObject(void)
{
	return (((0 != stopEvent_) || stopEvent_.create()) &&
		((0 != connectQueueEvent_) || connectQueueEvent_.create(true)));
}

bool SocketServer::initializeThread(void)
{
	TRACE_ENTRY("SocketServer::initializeThread");

	ASSERT(client_.empty()); // by this class

	bool retVal = false;

	// Start listening and save listener socket to index 0.
	SimpleSocket::SocketPtr listener(new SimpleSocket());
	if ((0 != listener) && listener->listen(port_, address_.c_str()))
	{
		// Create and associate listener event.
		listener_.reset(new SocketEvent(listener));
		retVal = ((0 != listener_) && listener_->initialize((FD_ACCEPT | FD_CLOSE)));
	}
	return retVal;
}

bool SocketServer::waitEvent(void)
{
	TRACE_ENTRY("SocketServer::waitEvent");

	// Create event array and wait.
	std::vector<HANDLE> events;
	events.reserve(client_.size() + 2);
	events.push_back(stopEvent_);
	events.push_back(listener_->getEventHandle());
	events.push_back(connectQueueEvent_);
	BOOST_FOREACH(SocketEventSP s, client_)
	{
		events.push_back(s->getEventHandle());
	}
	DWORD waitResult = ::WaitForMultipleObjects(events.size(), &events.front(), false, INFINITE);
	SHOW_LAST_ERROR((WAIT_FAILED != waitResult), "PortRouterClient::waitEvent", "::WaitForMultipleObjects");
	G_LOG_3_TRACE("SocketServer::waitEvent ['%s':%d], ::WaitForMultipleObjects returned %d", address_.c_str(), port_, waitResult);		// DEBUG hansohn 5/18/09

	// Check for wait error or stop event
	if ((WAIT_FAILED != waitResult) && (WAIT_OBJECT_0 != waitResult))
		return true;
	else
		return false;
}

bool SocketServer::handleEvent(void)
{
	TRACE_ENTRY("SocketServer::handleEvent");

	bool retVal = true;

	// Add any connections we initiated to "client" list.
	if (connectQueueEvent_.wait(0))
	{
		G_LOG_0_TRACE("SocketServer::handleEvent connectQueueEvent_.wait");
		CriticalSectionGuard g(connectQueueSynch_);
		while (retVal && !connectQueue_.empty())
		{
			TRACE_ENTRY("SocketServer::handleEvent processing connectQueue_ entry");
			if (this->handleConnect(connectQueue_.front()->getSocketPtr()))
			{
				G_LOG_2_TRACE("SocketServer::handleEvent client_.push_back for %s:%d", address_.c_str(), port_);
				client_.push_back(connectQueue_.front());
			}
			else
			{
				retVal = false;
			}
			connectQueue_.pop();
		}
		connectQueueEvent_.reset();
	}

	// Test clients before listener to ensure disconnects handled before reconnect.
	SocketEventArray::iterator iSocket = client_.begin();
	while (retVal && (client_.end() != iSocket))
	{
		// Flag if we need to increment iterator at the end of the loop.
		bool inc = true;

		// Get network events for socket.
		WSANETWORKEVENTS networkEvents;
		if (!(*iSocket)->enumNetworkEvents(networkEvents))
		{
			// in case the socket is closed remotely
			SS_TRACE0("SocketServer socketEvent::enumNetworkEvents(X) error");
			if (!this->handleDisconnect((*iSocket)->getSocketPtr()))
			{
				SS_TRACE0("SocketServer::handleDisconnect(0) error");
			}
			iSocket = client_.erase(iSocket);
			return true;
		}
		else
		{
			SS_TRACE1("SocketServer::handleEvent lNetworkEvents:%d", networkEvents.lNetworkEvents);

			// Check for socket closed.
			if (networkEvents.lNetworkEvents & FD_CLOSE)
			{
				SS_TRACE1("SocketServer::handleEvent FD_CLOSE(socket_:%d)", (SOCKET)(*(*iSocket)->getSocketPtr()));
				if (!this->handleDisconnect((*iSocket)->getSocketPtr()))
				{
					retVal = false;
					SS_TRACE0("SocketServer::handleDisconnect(1) error");
				}
				iSocket = client_.erase(iSocket);
				inc = false;
			}

			// Check for readable socket.  Because receiveMessage() does 2 reads, we get extra events.
			if ((networkEvents.lNetworkEvents & FD_READ) && (*iSocket)->getSocketPtr()->isReadable())
			{
				SS_TRACE1("SocketServer::handleEvent FD_READ(socket_:%d)", (SOCKET)(*(*iSocket)->getSocketPtr()));

				// Try to receive the message.
				std::string message;
				if (!(*iSocket)->getSocketPtr()->receiveMessage(message))
				{
					retVal = false;
					SS_TRACE0("SocketServer SimpleSocket::receiveMessage() error");
				}
				else
				{
					// Handle message.
					if (!message.empty())
					{
						if (!this->handleMessage((*iSocket)->getSocketPtr(), message))
						{
							retVal = false;
							SS_TRACE0("SocketServer::handleMessage() error");
						}
					}
					else // Successful receive with empty message indicates socket closed.
					{
						if (!this->handleDisconnect((*iSocket)->getSocketPtr()))
						{
							retVal = false;
							SS_TRACE0("SocketServer::handleDisconnect(2) error");
						}
						iSocket = client_.erase(iSocket);
						inc = false;
					}
				}
			}

			if (inc)
			{
				++iSocket;
			}
		}
	}
	
	// Check listener.
	if (retVal)
	{
		// TRACE_ENTRY("SocketServer::handleEvent checking listener");

		// Get network events and check for error or socket closed.
		WSANETWORKEVENTS networkEvents;
		if (!listener_->enumNetworkEvents(networkEvents) || (networkEvents.lNetworkEvents & FD_CLOSE))
		{
			retVal = false;
			SS_TRACE1("SocketServer %s", ((networkEvents.lNetworkEvents & FD_CLOSE)) ? "listener closed" : "socketEvent::enumNetworkEvents() error");
		}
		// Check for acceptable socket.
		else if (networkEvents.lNetworkEvents & FD_ACCEPT)
		{
			// Accept a new connection.
			SimpleSocket::SocketPtr sock = listener_->getSocketPtr()->accept();
			if (0 == sock)
			{
				SS_TRACE0("SocketServer accept() returned NULL");
			}
			if (0 != sock)
			{
				// Create and associate read event.
				SocketEventSP socketEvent(new SocketEvent(sock));
				if (!socketEvent->initialize(FD_READ | FD_CLOSE))
				{
					retVal = false;
					SS_TRACE0("SocketServer socketEvent::initialize() error");
				}
				else if (!this->handleConnect(sock))
				{
					retVal = false;
					SS_TRACE0("SocketServer this->handleConnect() error");
				}
				else
				{
					client_.push_back(socketEvent);
					SS_TRACE1("SocketServer client_.push_back(socketEvent: socket_:%d);", (SOCKET)(*sock));
				}
			}
		}
	}
	return retVal;
}

void SocketServer::signalStop(void)
{
	TRACE_ENTRY("SocketServer::signalStop");

	ASSERT(0 != stopEvent_);
	if (0 != stopEvent_)
	{
		stopEvent_.set();
	}
}

void SocketServer::finalize(void)
{
	G_LOG_2_TRACE("SocketServer::finalize ['%s':%d]", address_.c_str(), port_);

	// Cleanup sockets and events.
	client_.clear();
	listener_.reset();
	stopEvent_.reset();
}

}
